﻿using System;
using UnityEngine;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using UnityEditor;
using Debug = UnityEngine.Debug;
using System.Security.Cryptography;

public class ConfigEditor : EditorWindow
{
    public struct xml_info {
        public string name;
        public string md5;
    }
    private static List<xml_info> _Infos = new List<xml_info>();
    //导出脚本路径
    public static string scriptPath = "Assets/Scripts/Config";

    //导出XML路径
    public static string xmlPath = "Assets/Resources/Config/";

    //Excel表格路径（会递归查找所有子目录并导出）
    public static string excelRootPath;

    //是否导出脚本
    private static bool exportScript = true;

    //是否导出XML
    private static bool exportXml = true;

    //创建一个按钮用来打开我们自定义的窗口
    [MenuItem("MGFramework/配置文件", false, 1)]
    static void ConvertExcel()
    {
        Rect rect = new Rect(0f, 0f, 500f, 150f);
        EditorWindow win = EditorWindow.GetWindowWithRect<ConfigEditor>(rect, true, "配置文件", true);
        win.Show();
    }

    //窗口布局
    private void OnGUI()
    {
        EditorGUIUtility.labelWidth = 80;

        EditorGUILayout.BeginVertical();

        GUILayout.Space(20);

        GUILayout.BeginHorizontal();
        exportScript = EditorGUILayout.ToggleLeft("导出脚本", exportScript);
        GUILayout.Space(-180);
        EditorGUILayout.SelectableLabel("生成路径: " + scriptPath);
        if (GUILayout.Button("打开目录"))
        {
            System.Diagnostics.Process.Start(scriptPath.Replace('/', '\\'));
        }
        GUILayout.EndHorizontal();

        GUILayout.Space(5);

        GUILayout.BeginHorizontal();
        exportXml = EditorGUILayout.ToggleLeft("导出Xml", exportXml);
        GUILayout.Space(-180);
        EditorGUILayout.SelectableLabel("生成路径: " + xmlPath);
        if (GUILayout.Button("打开目录"))
        {
            //打开XML文件夹
            System.Diagnostics.Process.Start(xmlPath.Replace('/', '\\'));
        }
        GUILayout.EndHorizontal();

        GUILayout.Space(5);

        GUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Excel根目录");
        excelRootPath = EditorGUILayout.TextField(excelRootPath);
        excelRootPath = Application.dataPath + "/Editor/ExcelData";

        GUILayout.EndHorizontal();

        GUILayout.Space(5);

        if (GUILayout.Button("生成脚本"))
        {
            if (string.IsNullOrEmpty(excelRootPath))
            {
                EditorUtility.DisplayDialog("提示", "Excel路径不能为空", "确定");
                return;
            }
            //获取路径下所有以xls、xlsx文件的绝对路径
            List<string> files_1 = Directory.GetFiles(excelRootPath, "*.xls", SearchOption.AllDirectories).ToList();
            List<string> files_2 = Directory.GetFiles(excelRootPath, "*.xlsx", SearchOption.AllDirectories).ToList();

            List<String> files = new List<string>();
            
            for(int i = 0; i < files_1.Count; i++){
                files.Add(files_1[i]);
            }

            for(int i = 0; i < files_2.Count; i++){
                files.Add(files_2[i]);
            }

            //遍历Excel并执行导出方法
            for (int i = 0; i < files.Count; i++)
            {
                try
                {
                    //显示导出进度条
                    EditorUtility.DisplayProgressBar("converting...", files[i], (float)i / files.Count);
                    //调用导出方法传入表格路径
                    ExportCSharpClass(files[i]);
                }
                catch (Exception e)
                {
                    EditorUtility.ClearProgressBar();
                    throw new Exception(e.Source + "\n" + e.Message + "\n" + e.StackTrace);
                }
            }
            //存储配置表md5值的XML文件
            XmlDocument doc = new XmlDocument();
            XmlNode xml_info = doc.CreateElement("info");
            doc.AppendChild(xml_info);
            XmlNode item = null;
            StringBuilder code = new StringBuilder();
            //直接从网络下载全部配置表
            for (int i = 0; i < _Infos.Count; i++)
            {
                item = doc.CreateElement("ConfigInfo");
                XmlAttribute xmlid = doc.CreateAttribute("id");
                xmlid.Value = i.ToString();
                XmlAttribute xmlName = doc.CreateAttribute("name");
                xmlName.Value = _Infos[i].name;
                XmlAttribute xmlMd5 = doc.CreateAttribute("md5");
                xmlMd5.Value = _Infos[i].md5;
                item.Attributes.Append(xmlName);
                item.Attributes.Append(xmlMd5);
                xml_info.AppendChild(item);
            }
            doc.Save(xmlPath + "/ConfigInfo.xml");
            if(exportScript){
                code.Append("namespace MGFramework{\n\tpublic class LoadConfigData{");
                for(int i = 0; i < _Infos.Count; i++){
                     code.Append("\n\t\tpublic static "+_Infos[i].name+"Manager _"+_Infos[i].name + "Manager = new " + _Infos[i].name + "Manager();");
                }
                code.Append("\n\n\t\tpublic static void Init(){");
                for(int i = 0; i < _Infos.Count; i++){
                    code.Append("\n\t\t\t_"+_Infos[i].name+"Manager.LoadData();");
                }
                code.Append("\n\t\t}\n\t}\n}");
                if (!Directory.Exists(scriptPath)) Directory.CreateDirectory(scriptPath);
                
                string codePath = scriptPath + "/LoadConfigData.cs";
                //保存
                using (StreamWriter sw = new StreamWriter(codePath, false))
                {
                    sw.Write(code.ToString());
                    sw.Flush();
                }
            }
            _Infos.Clear();
            AssetDatabase.Refresh();
            EditorUtility.ClearProgressBar();
        }
        EditorGUILayout.EndVertical();
    }

    /// <summary>
    /// 导出Excel
    /// </summary>
    /// <param name="filePath">文件路径</param>
    public static void ExportCSharpClass(string filePath)
    {
        if (!exportScript && !exportXml) return;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Reset();
        stopwatch.Start();
        FileInfo info = new FileInfo(filePath);
        //去掉文件只读属性
        info.Attributes &= ~FileAttributes.ReadOnly;
       
        using (FileStream fs = File.Open(filePath, FileMode.Open))
        {
            //支持两种Excel格式，这里只读取Excel的第一张表
            //ISheet sheet = null;
            List<ISheet> sheetList = new List<ISheet>();
            if (filePath.EndsWith(".xls"))
            {
                HSSFWorkbook book = new HSSFWorkbook(fs);
                int sheet_count = book.Workbook.NumSheets;
                for (int i = 0; i < sheet_count; i++)
                {
                    sheetList.Add(book.GetSheetAt(i));
                }
                //sheet = book.GetSheetAt(0);
            }
            if (filePath.EndsWith(".xlsx")){
                XSSFWorkbook book2 = new XSSFWorkbook(fs);
                int sheet_count = book2.NumberOfSheets;
                for (int i = 0; i < sheet_count; i++){
                    sheetList.Add(book2.GetSheetAt(i));
                }
                //sheet = book2.GetSheetAt(0);
            }
            if (sheetList.Count == 0)
            {
                EditorUtility.DisplayDialog("提示", "解析错误！文件格式不支持！", "知道了");
                return;
            }

            for (int n = 0; n < sheetList.Count; n++) {
                //保存表格每列的名称，用于给cs脚本添加注释
                Dictionary<int, string> noteDict = new Dictionary<int, string>();
                //保存表格字段名称，用于创建脚本字段名称和XML标签名称
                Dictionary<int, string> fieldDict = new Dictionary<int, string>();
                //保存字段类型
                Dictionary<int, string> typeDict = new Dictionary<int, string>();
                XmlDocument doc = new XmlDocument();
                XmlNode root = doc.CreateElement("info");
                doc.AppendChild(root);
                StringBuilder code = new StringBuilder();

                ISheet sheet = sheetList[n];
                //通过两个循环给noteDict和fieldDict赋值
                for (int i = 0; i < 3; i++)
                {
                    IRow row = sheet.GetRow(i);
                    for (int j = 0; j < row.LastCellNum; j++)
                    {
                        ICell cell = row.GetCell(j);
                        string str = cell == null ? "" : cell.ToString();
                        if (i == 0)
                        {
                            noteDict.Add(j, str);
                        }
                        else if (i == 1)
                        {
                            typeDict.Add(j, str);
                        }
                        else if (i == 2)
                        {
                            fieldDict.Add(j, str);
                        }
                    }
                }
                string fileName = sheet.SheetName;
                int config_remark_index = -1;
                if (exportXml)
                {
                    XmlNode item = null;
                    for (int i = 0; i <= sheet.LastRowNum; i++)
                    {
                        IRow row = sheet.GetRow(i);
                        //表格前两行是汉字说明和字段名称
                        if (i > 2)
                        {
                            //单独读取ID字段
                            item = doc.CreateElement(sheet.SheetName);
                            if (row.GetCell(0) == null)
                            {
                                Debug.LogError(string.Format("第{0}行的id为空", i + 1));
                                return;
                            }
                            if (string.IsNullOrEmpty(row.GetCell(0).ToString()) || _Infos.Equals("config_remark")) continue;
                            for (int j = 0; j < fieldDict.Count; j++)
                            {
                                if (config_remark_index != j && row.GetCell(j) != null)
                                {
                                    XmlAttribute fieldName = doc.CreateAttribute(fieldDict[j]);
                                    fieldName.Value = row.GetCell(j).ToString();
                                    item.Attributes.Append(fieldName);
                                    root.AppendChild(item);
                                }
                            }
                        }
                        else if (i == 2) {
                            for (int j = 0; j < fieldDict.Count; j++)
                            {
                                if (row.GetCell(j).ToString().Equals("config_remark")) {
                                    config_remark_index = j;
                                }
                            }
                        }
                    }
                    //保存XML
                    string savePath = xmlPath + "/" + fileName + ".xml";
                    if (!Directory.Exists(xmlPath)) Directory.CreateDirectory(xmlPath);
                    doc.Save(savePath);
                    xml_info xinfo = new xml_info();
                    xinfo.name = fileName;
                    xinfo.md5 = ExportXmlMd5(savePath);
                    _Infos.Add(xinfo);
                }
                //导出cs代码
                if (exportScript)
                {
                    code.Append("using MGFramework;\nusing System.Collections.Generic;");
                    code.Append("\n\npublic class " + fileName + "{\n");
                    //根据字段名称创建字段
                    for (int i = 0; i < fieldDict.Count; i++)
                    {
                        string fieldName = fieldDict[i];
                        if (!fieldName.Equals("config_remark")) {
                            string type = typeDict[i];
                            noteDict[i] = noteDict[i].Replace("\n", "\n\t/// ");
                            code.Append("\n\n\t/// <summary>\n\t/// " + noteDict[i] + "\n\t/// </summary>");
                            code.Append("\n\tpublic " + type + " " + fieldName + ";");
                        }
                    }
                    code.Append("\n\n}");
                    code.Append("\n\npublic class " + fileName + "Manager{\n");
                    code.Append("\n\tprivate Dictionary<int, " + fileName + "> dict = new Dictionary<int, " + fileName + ">();\n\n\tprivate List<" + fileName + "> list = new List<" + fileName + ">();\n");
                    code.Append("\n\tpublic void LoadData(){\n\t\tlist = LocalConfigManager.Load<" + fileName + ">();\n\t\tfor (int i = 0; i < list.Count; i++) {\n\t\t\tdict.Add(list[i].id, list[i]);\n\t\t}\n\t}\n");
                    code.Append("\n\tpublic bool JudgeHasData(int id) {\n\t\treturn dict.ContainsKey(id);\n\t}\n");
                    code.Append("\n\tpublic " + fileName + " GetData(int id) {\n\t\tif (dict.ContainsKey(id)) {\n\t\t\treturn dict[id];\n\t\t}\n\t\treturn null;\n\t}\n");
                    code.Append("\n\tpublic List<" + fileName + "> GetList() {\n\t\treturn list;\n\t}\n");
                    code.Append("\n\tpublic Dictionary<int, " + fileName + "> GetDict() {\n\t\treturn dict;\n\t}\n}");
                    string codePath = scriptPath + "/" + fileName + ".cs";
                    if (!Directory.Exists(scriptPath)) Directory.CreateDirectory(scriptPath);
                    //保存
                    using (StreamWriter sw = new StreamWriter(codePath, false))
                    {
                        sw.Write(code.ToString());
                        sw.Flush();
                    }
                }
            }
        }
        stopwatch.Stop();
        Debug.Log("转换 "+filePath+" 耗时:" + stopwatch.Elapsed.TotalMilliseconds + "ms");
    }

    /// <summary>
    /// 计算文件的md5值
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    private static string ExportXmlMd5(string url) {
        if (File.Exists(url) == false)
            return string.Empty;
        byte[] fileByte = File.ReadAllBytes(url);
        if (fileByte == null)
            return string.Empty;
        byte[] hashByte = new MD5CryptoServiceProvider().ComputeHash(fileByte);
        return byteArrayToString(hashByte);
    }

    /// <summary>
    /// 输出数据的十六进制字符串
    /// </summary>
    /// <param name="arrInput"></param>
    /// <returns></returns>
    private static string byteArrayToString(byte[] arrInput)
    {
        StringBuilder sOutput = new StringBuilder(arrInput.Length);

        for (int i = 0; i < arrInput.Length; i++)
        {
            sOutput.Append(arrInput[i].ToString("X2"));
        }
        return sOutput.ToString();
    }
}